package com.fongtech.cli.common.config;

import com.fasterxml.jackson.core.JsonParseException;
import com.fongtech.cli.common.api.CommonResult;
import com.fongtech.cli.framework.enums.ErrorCodeEnum;
import com.fongtech.cli.framework.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.ShiroException;
import org.apache.shiro.authz.UnauthorizedException;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.List;
import java.util.Set;

/**
 * @ClassName GlobalExceptionHandler
 * @Description 全局异常处理
 * @Date 2019/8/7 15:11
 * @Version 1.0
 */
@ControllerAdvice
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 业务异常处理
     * @param e
     * @return
     */
    @ExceptionHandler(BusinessException.class)
    public CommonResult BusinessHandler(BusinessException e) {
        return CommonResult.failed(e.getErrorCode());
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public CommonResult badArgumentHandler(HttpRequestMethodNotSupportedException e) {
        return CommonResult.failed(ErrorCodeEnum.METHOD_NOT_ALLOWED);
    }

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public CommonResult badArgumentHandler(HttpMediaTypeNotSupportedException e) {
        return CommonResult.failed(ErrorCodeEnum.UNSUPPORTED_MEDIA_TYPE);
    }

    @ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
    public CommonResult badArgumentHandler(HttpMediaTypeNotAcceptableException e) {
        return CommonResult.failed(ErrorCodeEnum.NOT_ACCEPTABLE);
    }

    @ExceptionHandler(MissingPathVariableException.class)
    public CommonResult badArgumentHandler(MissingPathVariableException e) {
        StringBuilder builder = new StringBuilder("路径字段");
        builder.append(e.getVariableName());
        builder.append("校验不通过。描述: ");
        builder.append(e.getMessage());
        return CommonResult.failed(ErrorCodeEnum.BAD_REQUEST.convert(builder.toString()));
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    public CommonResult badArgumentHandler(MissingServletRequestParameterException e) {
        log.error("参数校验失败", e);
        StringBuilder builder = new StringBuilder("参数字段");
        builder.append(e.getParameterName());
        builder.append("校验不通过。描述：");
        builder.append(e.getMessage());
        return CommonResult.failed(ErrorCodeEnum.BAD_REQUEST.convert(builder.toString()));
    }

    @ExceptionHandler(ServletRequestBindingException.class)
    public CommonResult badArgumentHandler(ServletRequestBindingException e) {
        return CommonResult.failed(ErrorCodeEnum.BAD_REQUEST);
    }

    @ExceptionHandler(ConversionNotSupportedException.class)
    public CommonResult badArgumentHandler(ConversionNotSupportedException e) {
        return CommonResult.failed(ErrorCodeEnum.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(TypeMismatchException.class)
    public CommonResult badArgumentHandler(TypeMismatchException e) {
        log.error(e.getMessage(), e);
        return CommonResult.failed(ErrorCodeEnum.BAD_REQUEST);
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public CommonResult badArgumentHandler(HttpMessageNotReadableException e) {
        if(e.getCause() instanceof JsonParseException){
            return CommonResult.failed(ErrorCodeEnum.JSON_FORMAT_ERROR);
        }else{
            return CommonResult.failed(ErrorCodeEnum.BAD_REQUEST);
        }
    }

    @ExceptionHandler(HttpMessageNotWritableException.class)
    public CommonResult badArgumentHandler(HttpMessageNotWritableException e) {
        return CommonResult.failed(ErrorCodeEnum.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public CommonResult badArgumentHandler(MethodArgumentNotValidException e) {
        log.error(e.getMessage(), e);
        return CommonResult.failed(ErrorCodeEnum.BAD_REQUEST.convert(buildMessages(e.getBindingResult())));
    }

    private String buildMessages(BindingResult result) {
        StringBuilder resultBuilder = new StringBuilder();

        List<ObjectError> errors = result.getAllErrors();
        if (errors != null && errors.size() > 0) {
            for (ObjectError error : errors) {
                if (error instanceof FieldError) {
                    FieldError fieldError = (FieldError) error;
                    String fieldName = fieldError.getField();
                    String fieldErrMsg = fieldError.getDefaultMessage();
                    resultBuilder.append(fieldName).append(" ").append(fieldErrMsg).append(";");
                }
            }
        }
        return resultBuilder.toString();
    }

    @ExceptionHandler(MissingServletRequestPartException.class)
    public CommonResult badArgumentHandler(MissingServletRequestPartException e) {
        return CommonResult.failed(ErrorCodeEnum.BAD_REQUEST);
    }

    @ExceptionHandler(BindException.class)
    public CommonResult badArgumentHandler(BindException e) {
        return CommonResult.failed(ErrorCodeEnum.BAD_REQUEST);
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    public CommonResult badArgumentHandler(NoHandlerFoundException e) {
        return CommonResult.failed(ErrorCodeEnum.NOT_FOUND);
    }

    @ExceptionHandler(AsyncRequestTimeoutException.class)
    public CommonResult badArgumentHandler(AsyncRequestTimeoutException e) {
        return CommonResult.failed(ErrorCodeEnum.SERVICE_UNAVAILABLE);
    }

    @ExceptionHandler(ValidationException.class)
    public CommonResult badArgumentHandler(ValidationException e) {
        log.error(e.getMessage(), e);
        if (e instanceof ConstraintViolationException) {
            ConstraintViolationException exs = (ConstraintViolationException) e;
            Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
            for (ConstraintViolation<?> item : violations) {
                String message = ((PathImpl) item.getPropertyPath()).getLeafNode().getName() + item.getMessage();
                return CommonResult.failed(message);
            }
        }
        return CommonResult.failed(ErrorCodeEnum.BAD_REQUEST);
    }

    @ExceptionHandler(UnauthorizedException.class)
    public CommonResult badArgumentHandler(UnauthorizedException e) {
        return CommonResult.failed(ErrorCodeEnum.FORBIDDEN);
    }

    @ExceptionHandler(ShiroException.class)
    public CommonResult badArgumentHandler(ShiroException e) {
        return CommonResult.failed(ErrorCodeEnum.UNAUTHORIZED);
    }

    @ExceptionHandler(Exception.class)
    public Object seriousHandler(Exception e) {
        log.error(e.getMessage(), e);
        return CommonResult.failed(ErrorCodeEnum.FAILED);
    }
}

